import net from 'net'
import output from '@/utils/output'
import { db } from './db'
import { Project } from './project'
import { Limiter } from './limiter'
import { globalLogger } from '@/log'
import { ServerConnect } from './connect'
import type { ParsedServerConfig } from '@/types/configFile'

export class Server {
  constructor(config: ParsedServerConfig, debugConfig: ServerDebugConfig) {
    this.config = config
    this.projects = config.project.map((c) => new Project(c, debugConfig))
    this.server = this.createServer()
    this.connects = []
    this.limiter = new Limiter(this.db)
    this.debugConfig = debugConfig
    this.bind()
    this.listen()
  }

  readonly config: ParsedServerConfig
  readonly server: net.Server
  readonly projects: Project[]
  readonly connects: ServerConnect[]
  readonly db = db
  readonly limiter: Limiter
  readonly debugConfig: ServerDebugConfig

  createServer() {
    const server = net.createServer((socket) => {
      const connect = new ServerConnect(socket, this)
      this.connects.push(connect)

      const addressInfo = ` ${connect.remoteAddressInfo.family} ${connect.remoteAddressInfo.address}:${connect.remoteAddressInfo.port}`
      const msg = `connect${addressInfo} ${connect.id}`

      output(msg)
      globalLogger.server.info(msg)

      connect.on('end', () => {
        const endMsg = `Connection end${addressInfo} ${connect.id}`
        globalLogger.server.info(endMsg)
        output(endMsg)

        const index = this.connects.findIndex((item) => item.id === connect.id)
        if (index === -1) {
          globalLogger.server.error('Connection ended, index find failed')
        } else {
          this.connects.splice(index, 1)
        }
      })
    })
    process.on('exit', () => this.close())
    process.on('SIGINT', () => this.close())
    return server
  }

  private bind() {
    this.server.on('close', () => {
      const msg = 'server closed'
      output(msg)
      globalLogger.server.info(msg)
    })

    this.server.on('error', (err: NodeJS.ErrnoException) => {
      globalLogger.server.error(err)

      if (err.code === 'EADDRINUSE') {
        const msg = `port ${this.config.port} is already in use`
        output.error(msg)
        globalLogger.server.error(msg)
        this.server.close()
        this.server.once('close', () => process.exit())
      }
    })
  }

  listen() {
    this.server.listen(this.config.port, () => {
      const msg = `server started, listening port: ${this.config.port}`
      output(msg)
      globalLogger.server.info(msg)
    })
  }

  async close() {
    for (let i = 0; i < this.connects.length; i++) {
      if (!this.connects[i].socket.destroyed) await this.connects[i].end()
    }

    if (this.server.listening) {
      this.server.close((err) => {
        if (err) {
          globalLogger.server.error(err)
        }
        process.exit()
      })
    }
  }

  getProjectByName(name: string) {
    return this.projects.find((item) => item.name === name)
  }

  projectExistByName(name: string) {
    return this.projects.some((item) => item.name === name)
  }
}

export type ServerDebugConfig = {
  debug: boolean
  notUpdateHashMap?: true
}
